package com.java.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.java.item.entity.*;
import com.java.search.feign.GoodsApiFeign;
import com.java.search.pojo.Goods;
import com.java.search.pojo.SearchRequest;
import com.java.search.pojo.SearchResult;
import com.java.search.repository.GoodsRepository;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.LongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author jl
 * @description  聚合查询：根据页面请求的过滤条件被选项,动态显示 SearchResult 对象的数据(页面过滤可选项数据 和 分页商品搜索结果)
 * @date 2019-10-25 16:53
 */
@Service
public class SearchService {
    @Autowired
    private GoodsApiFeign goodsApiFeign;
    @Autowired
    private GoodsRepository goodsRepository;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    public Goods buildGoods(Spu spu) throws IOException {
        //根据分类ids查询分类名称集合
        List<String> categoryNames = goodsApiFeign.queryCategoryNamesByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));
        //根据brandId查询品牌
        Brand brand = goodsApiFeign.queryBrandById(spu.getBrandId());
        //查询spu下的所有sku
        List<Sku> skus = goodsApiFeign.querySkusBySpuId(spu.getId());
        List<Long> prices = skus.stream().map(Sku::getPrice).collect(Collectors.toList());
        //收集sku的必要字段,前端不需要sku的全部字段
        List<Map<String, Object>> skuMapList = skus.stream().map(e -> {
            Map<String, Object> map = new HashMap<>();
            map.put("id", e.getId());
            map.put("title", e.getTitle());
            map.put("price", e.getPrice());
            map.put("image", StringUtils.isEmpty(e.getImages()) ? "" : StringUtils.split(e.getImages(), ",")[0]);
            return map;
        }).collect(Collectors.toList());
        //根据spu中的cid3查询出所有的搜索规格参数
        List<SpecParam> specParams = goodsApiFeign.queryParams(null, spu.getCid3(), null, true);
        //获取spuDetail
        SpuDetail spuDetail = goodsApiFeign.queryDetailBySpuId(spu.getId());
        //获取通用的规格参数值
        Map<String,Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(),new TypeReference<Map<String,Object>>(){});
        //获取特殊的规格参数值
        Map<String,List<Object>> specialSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(),new TypeReference<Map<String,List<Object>>>(){});

        Map<String, Object> specs = new HashMap<>();
        specParams.forEach(param->{
            //判断规格参数的类型,是否是通用的规格参数
            if (param.getGeneric()) {
                //如果是通用类型的参数,从genericSpecMap中获取参数值
                String value = String.valueOf(genericSpecMap.get(param.getId().toString()));
                //判断是否是数值类型,返回一个区间
                if (param.getNumeric()) {
                    value = chooseSegment(value, param);
                }
                specs.put(param.getName(), value);
            } else {
                //如果是特殊类型的参数,从specialSpecMap中获取参数值
                List<Object> value =specialSpecMap.get(param.getId().toString());
                specs.put(param.getName(), value);
            }
        });

        Goods goods = new Goods();
        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime());
        goods.setSubTitle(spu.getSubTitle());
        //拼接all 标题+分类名称+品牌名称
        goods.setAll(spu.getTitle()+" "+ StringUtils.join(categoryNames," ") +" "+brand.getName());
        //获取spu下的所有sku的价格集合
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));
        //获取spu下的所有sku集合并转化成json字符串
        goods.setPrice(prices);
        //获取所有的查询参数{name:value}
        goods.setSpecs(specs);

        return goods;
    }

    /**
     * 把tb_spec_param表中的segments字段 解析为前端显示的可搜索范围字段
     */
    private String chooseSegment(String value, SpecParam specParam) {
        String[] split = specParam.getSegments().split(",");
        //使用NumberUtils工具类把字符串解析为double值
        Double val = NumberUtils.toDouble(value);
        String result = "其他";
        //获取每一个区间值，判断数值value是否在对应区间中
        for (String s : split) {
            String[] spl = s.split("-");
            //获取每个数值区间的起始区间
            Double begin = NumberUtils.toDouble(spl[0]);
            //因为有可能不存在结束区间，如1000以上，故end先取一个最大值
            Double end = Double.MAX_VALUE;
            //判断该数值区间是否为一个闭区间，如果为闭区间，则表示存在结束区间，应将end的取值为对应结束区间的值
            if (spl.length == 2) {
                end = NumberUtils.toDouble(spl[1]);
            }
            //获得了起始区间和结束区间后，开始判断传过来的value对应的值属于哪个区间
            if (val < end && val > begin) {
                if (spl.length == 1) {
                    result = spl[0] + specParam.getUnit() + "以上";
                } else {
                    result = s + specParam.getUnit();
                }
                break;
            }
        }
        return result;
    }

    public SearchResult search(SearchRequest request) {
        if (request.getKey() == null) {
            return null;
        }
        //自定义查询构建起
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加查询条件
        //matchQuery 匹配查询all字段
//        QueryBuilder basicQuery = QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND);
        BoolQueryBuilder basicQuery = buildBoolQueryBuilder(request);
        queryBuilder.withQuery(basicQuery);
        //添加分页
        queryBuilder.withPageable(PageRequest.of(request.getPage()-1,request.getSize()));
        //添加结果集过滤,使结果集只包含id字段、skus字段、subTitle字段 无排除字段
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id","skus","subTitle"},null));

        //添加分类和品牌的聚合
        String categoryAggName="categories";
        String brandAggName="brands";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAggName).field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAggName).field("brandId"));

        //执行查询
        AggregatedPage<Goods> goodsPage =(AggregatedPage<Goods>)goodsRepository.search(queryBuilder.build());

        //获取聚合结果集并解析
        List<Map<String,Object>> categories = getCategoryAggResult(goodsPage.getAggregation(categoryAggName));
        List<Brand> brands = getBrandAggResult(goodsPage.getAggregation(brandAggName));

        //判断是否是一个分类,只有一个分类时才做规格参数聚合
        List<Map<String,Object>> specs = new ArrayList<>();
        if (!CollectionUtils.isEmpty(categories) && categories.size() ==1) {
            //对规格参数进行聚合
            specs = getParamAggResult((Long)categories.get(0).get("id"),basicQuery);
        }


        return new SearchResult(goodsPage.getTotalElements(),Integer.valueOf(goodsPage.getTotalPages()).longValue(),goodsPage.getContent(),categories,brands,specs);
    }

    private BoolQueryBuilder buildBoolQueryBuilder(SearchRequest request) {
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        //给布尔查询添加基本查询条件
        queryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));
        //添加过滤条件
        //获取用户选择的过滤信息
        Map<String, String> filter = request.getFilter();
        for (Map.Entry<String, String> entry : filter.entrySet()) {
            String key = entry.getKey();
            if (StringUtils.equals(key, "分类")) {
                key = "cid3";
            } else if (StringUtils.equals(key, "品牌")) {
                key = "brandId";
            } else {
                key = "specs."+key+".keyword";
            }
            queryBuilder.filter(QueryBuilders.termQuery(key,entry.getValue()));
        }
        return queryBuilder;
    }

    /**
     * 根据查询条件聚合规格参数
     */
    private List<Map<String, Object>> getParamAggResult(Long id, QueryBuilder basicQuery) {
        //自定义查询构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //添加基本查询条件
        queryBuilder.withQuery(basicQuery);
        //查询要聚合的规格参数
        List<SpecParam> specParams = goodsApiFeign.queryParams(null, id, null, true);
        //添加规格参数的聚合
        specParams.forEach(s-> queryBuilder.addAggregation(AggregationBuilders.terms(s.getName()).field("specs."+s.getName()+".keyword")));
        //结果集过滤
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{},null));
        //执行聚合查询
        AggregatedPage<Goods> goodsPage =(AggregatedPage<Goods>)goodsRepository.search(queryBuilder.build());

        List<Map<String, Object>> specs = new ArrayList<>();
        //获取所有的聚合结果进行解析 key-聚合名称(规格参数名) value-聚合对象
        Map<String, Aggregation> aggregationMap = goodsPage.getAggregations().asMap();
        for (Map.Entry<String, Aggregation> entry : aggregationMap.entrySet()) {
            //{k:规格参数名,options:集合的规格参数值}
            Map<String, Object> map = new HashMap<>();
            map.put("k",entry.getKey());
            //获取聚合
            StringTerms terms =(StringTerms) entry.getValue();

            //初始化一个options集合,收集桶中的key
            List<String> options = new ArrayList<>();
            //获取桶集合
            terms.getBuckets().forEach(b-> options.add(b.getKeyAsString()));
            map.put("options",options);
            specs.add(map);
        }
        return specs;
    }

    /**
     * 解析品牌的聚合结果集
     */
    private List<Brand> getBrandAggResult(Aggregation aggregation) {
        LongTerms terms = (LongTerms) aggregation;
        //获取聚合中的桶
        return terms.getBuckets().stream().map(b -> goodsApiFeign.queryBrandById(b.getKeyAsNumber().longValue())).collect(Collectors.toList());
    }

    /**
     * 解析分类的聚合结果集
     */
    private List<Map<String,Object>> getCategoryAggResult(Aggregation aggregation) {
        LongTerms terms = (LongTerms) aggregation;
        List<Map<String, Object>> categories = terms.getBuckets().stream().map(b -> {
            Map<String, Object> map = new HashMap<>();
            Long cid = b.getKeyAsNumber().longValue();
            List<String> names = goodsApiFeign.queryCategoryNamesByIds(Collections.singletonList(cid));
            map.put("id", cid);
            map.put("name", names.get(0));
            return map;
        }).collect(Collectors.toList());
        return categories;
    }

    public void save(Long id) throws IOException {
        Spu spu = goodsApiFeign.querySpuById(id);
        Goods goods = buildGoods(spu);
        goodsRepository.save(goods);
    }

    public void delete(Long id) {
        goodsRepository.deleteById(id);
    }
}
